Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: Add StaticStates wrapper #377

Merged
merged 25 commits into from Dec 26, 2019
Merged

chore: Add StaticStates wrapper #377

merged 25 commits into from Dec 26, 2019

Conversation

mannycarrera4
Copy link
Contributor

@mannycarrera4 mannycarrera4 commented Dec 16, 2019

Fixes: #375

This is separating the work in #271

This creates a wrapper for stories to allow for treating pseudo-selectors like :hover or :focus as static states that are controllable as classes rather than the default pseudo-selectors.

For example, this is now a valid story:

.add('States', () => {
  <StaticStates>
    <Button className="focus">Focus</Button>
  </StaticStates>
});

This code will render a button with focus styling always applied. This is useful for enumerating states for design or validating states in visual regression tests.

To enable this functionality, Components must use the Canvas-kit styled function instead of the one from Emotion. The canvas-kit styled function also seamlessly handles a fallback to default theme and RTL support out-of-the-box.

@cypress
Copy link

cypress bot commented Dec 16, 2019



Test summary

56 0 0 0


Run details

Project canvas-kit
Status Passed
Commit 92f4c47
Started Dec 26, 2019 8:11 PM
Ended Dec 26, 2019 8:11 PM
Duration 00:46 💡
OS Linux Ubuntu Linux - 16.04
Browser Electron 61

View run in Cypress Dashboard ➡️


This comment has been generated by cypress-bot as a result of this project's GitHub integration settings. You can manage this integration in this project's settings in the Cypress Dashboard

utils/storybook/CanvasProviderDecorator.tsx Outdated Show resolved Hide resolved
utils/storybook/CanvasProviderDecorator.tsx Outdated Show resolved Hide resolved
modules/_labs/core/react/lib/CanvasProvider.tsx Outdated Show resolved Hide resolved
modules/common/react/lib/utils/changeToStaticClass.ts Outdated Show resolved Hide resolved
modules/common/react/lib/utils/changeToStaticClass.ts Outdated Show resolved Hide resolved
@anicholls
Copy link
Contributor

Thoughts on loading Default stories first and States stories last by updating storySort as part of this PR?

@mannycarrera4
Copy link
Contributor Author

@anicholls that makes sense to me. In the icon button PR, i had the states as a last story after seeing all the defaults!

@lychyi lychyi added this to In progress in Current Sprint (7/20 - 8/9) via automation Dec 18, 2019
modules/common/react/lib/utils/changeToStaticStates.ts Outdated Show resolved Hide resolved
modules/common/react/lib/utils/changeToStaticStates.ts Outdated Show resolved Hide resolved
modules/button/react/stories/stories_iconButton.tsx Outdated Show resolved Hide resolved
modules/common/react/lib/utils/changeToStaticStates.ts Outdated Show resolved Hide resolved
modules/common/react/lib/utils/changeToStaticStates.ts Outdated Show resolved Hide resolved
modules/_labs/core/react/README.md Outdated Show resolved Hide resolved
modules/button/react/lib/Button.tsx Outdated Show resolved Hide resolved
modules/button/react/stories/stories.tsx Outdated Show resolved Hide resolved
modules/common/react/spec/changeToStaticStates.spec.ts Outdated Show resolved Hide resolved
const prefixFn = pipe(
prefix('welcome-', '0'),
prefix('getting-started', '0'),
prefix('tokens-', '1'),
prefix('components-', '2'),
prefix('components-', '3'),
prefix('states', '4'),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This works, but I think we may want to do this separately from this calculation. This is specifically meant to target the prefix from the categories at the top level of storybook. I believe this could result in some unexpected edge cases for something like a story named Tokens|Foo/Bar/States. I would suggest doing a separate check after the number prefix has already been added.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function replaced another function that only handled top-level categories. The input of that function was something like ['Welcome', 'Tokens', 'Components'], but I also needed to make sure "Getting Started" was the first item under "Welcome" so that Storybook picked it as the default page. Numbers were chosen because sorting alphabetically ensures numbers rank higher than letters, so '0first' will come before 'first'. Also numbers are easy for us to understand in terms of "ranking".

This function is definitely a hack, but the intent is to be powerful enough to handle sorting on a global level while still being easy enough to add new sorting rules. I actually think adding prefix('states', '3') before the prefix('components-', '3') would accomplish the same goal without introducing a new sorting hierarchy ('3' would be prefixed for having 'states' in the storyname first, then '3' would be prefixed first for being in the "Components" category).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I retract the 3. It has to be a 4 to not be the first story in Components... Perhaps not very intuitive

.storybook/config.js Outdated Show resolved Hide resolved
.storybook/config.js Outdated Show resolved Hide resolved
@anicholls
Copy link
Contributor

anicholls commented Dec 19, 2019

I just realized that we have the same problem with this that we did when we tried to add a dir prop to components for RTL. It worked in the example case you tried (buttons) because we only have states on the button element and we spread extra props on that element. But for components with pseudo states in multiple nested styled components, we need to ensure the _shouldConvertPseudoStatesToClasses prop is passed to every styled component.

@NicholasBoll I think this is probably okay to go in as is, but we really need to look at re-writing the styled factory so we can inject our own props like this. If we don't, it's going to add a lot of noise/complexity to our components very quickly.

@NicholasBoll
Copy link
Member

I just realized that we have the same problem with this that we did when we tried to add a dir prop to components for RTL. It worked in the example case you tried (buttons) because we only have states on the button element and we spread extra props on that element. But for components with pseudo states in multiple nested styled components, we need to ensure the _shouldConvertPseudoStatesToClasses prop is provided for every styled component.

@NicholasBoll I think this is probably okay to go in as is, but we really need to look at re-writing the styled factory so we can inject our own props like this. If we don't, it's going to add a lot of noise/complexity to our components very quickly.

Agreed. I think the ideal implementation is using a Context where a Provider (we originally called it StaticState) when wrapping a component will provide a context value that initiates the static state functionality and put it in the StyledComponent component. This way, the functionality is easy to initiate from the documentation in a way that each component doesn't have to know anything about.

We tried it in the styled.ts factory function, but Context is only available to components. We'd either have to create a new wrapper for every component or rewrite the styled function from emotion to add this functionality to the StyledComponent component. I didn't like the idea of creating an extra component wrapper around every styled component and it also sounds like more maintenance for our own version of styled.

For a better developer experience, I think we'd take on the maintenance burden of maintaining our own styled wrapper to make it seamless.

@NicholasBoll NicholasBoll changed the title chore: Update CanvasProvider and add util for static pseudo states chore: Update CanvasProvider and add StaticStates wrapper Dec 20, 2019
@NicholasBoll
Copy link
Member

After talking with @anicholls, it is possible to use our styled from the labs/themes as long as we use the theme context which is provided to our interpolations. We are already doing this with RTL, so we're just tacking on.

I opted out of having the boolean parameter part of CanvasTheme, so it is a private boolean.

const prefix = (phrase, prefix) => value => (value.indexOf(phrase) > -1 ? prefix + value : value);
const prefix = (phrase, prefix) => (/** @type {string} */ value) => {
const index = value.indexOf(phrase);
return index > -1 ? value.substr(0, index) + prefix + value.substr(index) : value;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change allows for multi-tier sorting instead of a global sorting. For example, "Welcome" is the category and "getting-started" is the item. The below will produce a string like:

"0welcome--agetting-started"

Since the "a" is now added before "getting-started" instead of before "welcome", it allows sorting to be local to other items. Lowercase letters should be used in multi-tier sorting so it flows naturally with other items without disrupting them. For example, numbers are always before lowercase letters in ASCII. So even if you add a '9', it will sort first rather than the desired last which is why z is used below

.replace(/^:/, '&:') // handle shorthand like ":focus"
.replace(/,(\s+):/g, ',$1&:') // handle selectors like ":focus, :hover"
.replace(/:(focus|hover|active)/g, '.$1')
.replace(/\[data\-whatinput="?(mouse|touch|keyboard|pointer)"?]/g, '[data-whatinput="noop"]');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Disables the InputProvider style overrides

function styled<Props>(node: any) {
return (...args: Interpolation<Props>[]) => {
const newArgs: Interpolations = args.map(
interpolation => (props: Props & {theme: CanvasTheme; direction: ContentDirection}) => {
interpolation => (props: Props & {theme: CanvasTheme & {_staticStates?: boolean}}) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not adding _staticStates to CanvasTheme keeps it private

@mannycarrera4 mannycarrera4 changed the title chore: Update CanvasProvider and add StaticStates wrapper chore: Add StaticStates wrapper Dec 24, 2019
@NicholasBoll NicholasBoll dismissed anicholls’s stale review December 26, 2019 21:21

Changes requested are now outdated

@NicholasBoll NicholasBoll merged commit ed41a76 into Workday:master Dec 26, 2019
Current Sprint (7/20 - 8/9) automation moved this from In progress to Done Dec 26, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
No open projects
Development

Successfully merging this pull request may close these issues.

Improve CanvasProvider to support optional InputProvider and add new utility to styled function
3 participants